package xorm

import (
	"gitee.com/KotlinToGo/xorm/internal/utils"
	"gitee.com/KotlinToGo/xorm/schema"
	"sync"
)

const (
	Required    = 0 //支持当前事务；如果不存在，请创建一个新的。( 原PropagationRequired)
	Support     = 1 //支持当前事务；如果不存在，则以非事务方式执行。(原PropagationSupports)
	Mandatory   = 2 //支持当前事务；如果当前事务不存在，则返回错误。
	RequiredNew = 3 //创建一个新事务，如果存在则暂停当前事务。
	NotSupport  = 4 //不支持当前事务；而是始终以非事务方式执行。
	Never       = 5 //不支持当前事务；如果当前事务存在，则返回错误。
	Nested      = 6 //如果当前事务存在，则在嵌套事务中执行，其他行为类似于 Required 。
	NotRequired = 7
)

// Tx 事务
type Tx struct {
	s             *Session
	txDefinition int
	isNested              bool
	savePointID           string
}

func (t *Tx) TxDefinition() int {
	return t.txDefinition
}

// IsExistTx 事务存在事务
func (t *Tx) IsExistTx() bool {
	if t.s.tx == nil {
		return false
	} else {
		return true
	}
}

func (t *Tx) GetSavePointID() string {
	return t.savePointID
}

func (t *Tx) Session() *Session {
	return t.s
}

func (t *Tx) Do(doFunc func(params ...interface{}), params ...interface{}) {
	if t.isNested {
		go doFunc(params...)
	} else {
		doFunc(params...)
	}
}

func (t *Tx) WaitForDo(doFunc func(params ...interface{}), params ...interface{}) {
	if t.isNested {
		var w sync.WaitGroup
		w.Add(1)
		go func() {
			doFunc(params...)
			w.Done()
		}()
		w.Wait()
	} else {
		doFunc(params...)
	}
}

func (s *Session) BeginTx(transactionDefinition ...int) (*Tx, error) {
	var tx *Tx
	if len(transactionDefinition) == 0 {
		tx = s.transaction(Required)
	} else {
		tx = s.transaction(transactionDefinition[0])
	}

	err := tx.BeginTx()
	if err != nil {
		return nil, err
	}
	return tx, nil
}

func (s *Session) transaction(transactionDefinition int) *Tx {
	if transactionDefinition > 6 || transactionDefinition < 0 {
		return &Tx{s: s, txDefinition: Required}
	}
	return &Tx{s: s, txDefinition: transactionDefinition}
}

// BeginTx 开启一个事务
func (t *Tx) BeginTx() error {
	switch t.txDefinition {
	case Required:
		if !t.IsExistTx() {
			if err := t.s.Begin(); err != nil {
				return err
			}
		} else {
			if t.s.currentTx != nil {
				t.savePointID = t.s.currentTx.savePointID
			}
			t.isNested = true
		}
		t.s.currentTx = t
		return nil
	case Support:
		if t.IsExistTx() {
			t.isNested = true
			if t.s.currentTx != nil {
				t.savePointID = t.s.currentTx.savePointID
			}
			t.s.currentTx = t
		}
		return nil
	case Mandatory:
		if !t.IsExistTx() {
			return ErrNestedTransaction
		} else {
			if t.s.currentTx != nil {
				t.savePointID = t.s.currentTx.savePointID
			}
			t.isNested = true
			t.s.currentTx = t
		}
		return nil
	case RequiredNew:
		t.s = t.s.engine.NewSession()
		if err := t.s.Begin(); err != nil {
			return err
		}
		t.isNested = false
		t.s.currentTx = t
		return nil
	case NotSupport:
		if t.IsExistTx() {
			t.isNested = true
			t.s = t.s.engine.NewSession()
		}
		return nil
	case Never:
		if t.IsExistTx() {
			return ErrNestedTransaction
		}
		return nil
	case Nested:
		if !t.IsExistTx() {
			if err := t.s.Begin(); err != nil {
				return err
			}
		} else {
			t.isNested = true
			dbtype := t.s.engine.Dialect().URI().DBType
			if dbtype == schema.Mssql{
				t.savePointID = "xorm" + utils.NewShortUUID().String()
			} else {
				t.savePointID = "xorm" + utils.NewV1().WithoutDashString()
			}

			if err := t.SavePoint(t.savePointID); err != nil {
				return err
			}
			t.s.autoCommit = false
			t.s.commitOrRollback = false
			t.s.currentTx = t

		}
		return nil
	case NotRequired:
		if t.IsExistTx() {
			return ErrNestedTransaction
		}

		if err := t.s.Begin(); err != nil {
			return err
		}
		return nil
	default:
		return ErrTransactionDefinition
	}

}

// Commit 使用事务时，提交所有操作。
func (t *Tx) CommitTx() error {
	switch t.txDefinition {
	case Required :
		if !t.IsExistTx() {
			return ErrNotInTransaction
		}
		if !t.isNested {
			err := t.s.Commit()
			if err != nil {
				return err
			}
		}
		return nil
	case Support:
		if t.IsExistTx() {
			if !t.isNested {
				err := t.s.Commit()
				if err != nil {
					return err
				}
			}
		}
		return nil
	case Mandatory:
		if !t.IsExistTx() {
			return ErrNotInTransaction
		}
		if !t.isNested {
			err := t.s.Commit()
			if err != nil {
				return err
			}
		}
		return nil
	case RequiredNew:
		if !t.IsExistTx() {
			return ErrNotInTransaction
		}
		if !t.isNested {
			err := t.s.Commit()
			if err != nil {
				return err
			}
		}
		return nil
	case NotSupport:
		if t.IsExistTx() {
			return ErrNestedTransaction
		}
		return nil
	case Never:
		if t.IsExistTx() {
			return ErrNestedTransaction
		}
		return nil
	case Nested:
		if !t.IsExistTx() {
			return ErrNotInTransaction
		}

		if !t.isNested {
			err := t.s.Commit()
			if err != nil {
				return err
			}
		} else if t.s.rollbackSavePointID == t.savePointID {
			if err := t.RollbackToSavePoint(t.savePointID); err != nil {
				t.s.rollbackSavePointID = ""
				return err
			}
		}
		return nil
	case NotRequired:
		if !t.IsExistTx() {
			return ErrNotInTransaction
		}
		if !t.isNested {
			err := t.s.Commit()
			if err != nil {
				return err
			}
		} else {
			return ErrNestedTransaction
		}
		return nil
	default:
		return ErrTransactionDefinition
	}
}

// Rollback 使用事务时，如果有任何错误，您可以回滚
func (t *Tx) RollbackTx() error {
	switch t.txDefinition {
	case Required:
		if !t.IsExistTx() {
			return ErrNotInTransaction
		}
		if t.savePointID == "" {
			err := t.s.Rollback()
			if err != nil {
				return err
			}
		} else {
			t.s.rollbackSavePointID = t.savePointID
		}

		return nil
	case Support:
		if t.IsExistTx() {
			if t.savePointID == "" {
				err := t.s.Rollback()
				if err != nil {
					return err
				}
			} else {
				t.s.rollbackSavePointID = t.savePointID
			}
			return nil
		}
		return nil
	case Mandatory:
		if !t.IsExistTx() {
			return ErrNotInTransaction
		}
		if t.savePointID == "" {
			err := t.s.Rollback()
			if err != nil {
				return err
			}
		} else {
			t.s.rollbackSavePointID = t.savePointID
		}
		return nil

	case RequiredNew:
		if !t.IsExistTx() {
			return ErrNotInTransaction
		}
		err := t.s.Rollback()
		if err != nil {
			return err
		}
		return nil
	case NotSupport:
		if t.IsExistTx() {
			return ErrNestedTransaction
		}
		return nil
	case Never:
		if t.IsExistTx() {
			return ErrNestedTransaction
		}
		return nil
	case Nested:
		if !t.IsExistTx() {
			return ErrNotInTransaction
		}

		if t.s.rollbackSavePointID == t.savePointID {
			return nil
		}

		if t.isNested {
			if err := t.RollbackToSavePoint(t.savePointID); err != nil {
				return err
			}
			return nil
		} else {
			err := t.s.Rollback()
			if err != nil {
				return err
			}
			return nil
		}
	case NotRequired:
		if !t.IsExistTx() {
			return ErrNotInTransaction
		}

		err := t.s.Rollback()
		if err != nil {
			return err
		}
		return nil
	default:
		return ErrTransactionDefinition
	}
}

func (t *Tx) SavePoint(savePointID string) error {
	if t.s.tx == nil {
		return ErrNotInTransaction
	}

	var lastSQL string
	dbtype := t.s.engine.Dialect().URI().DBType
	if dbtype == schema.Mssql {
		lastSQL = "save tran " + savePointID
	} else {
		lastSQL = "SAVEPOINT " + savePointID + ";"
	}

	t.s.saveLastSQL(lastSQL)
	if _, err := t.s.tx.Exec(lastSQL); err != nil {
		return err
	}

	return nil
}

func (t *Tx) RollbackToSavePoint(savePointID string) error {
	if t.s.tx == nil {
		return ErrNotInTransaction
	}

	var lastSQL string
	dbtype := t.s.engine.Dialect().URI().DBType
	if dbtype == schema.Mssql {
		lastSQL = "rollback tran " + savePointID
	} else {
		lastSQL = "ROLLBACK TO SAVEPOINT " + t.savePointID + ";"
	}

	t.s.saveLastSQL(lastSQL)
	if _, err := t.s.tx.Exec(lastSQL); err != nil {
		return err
	}

	return nil
}
